﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using System.Diagnostics.CodeAnalysis;
using WasmAppBuilder;

//
// This class generates the icall_trampoline_dispatch () function used by the interpreter to call native code on WASM.
// It should be kept in sync with mono_wasm_interp_to_native_trampoline () in the runtime.
//

#nullable enable

internal sealed class InterpToNativeGenerator
{
    private LogAdapter Log { get; set; }

    public InterpToNativeGenerator(LogAdapter log) => Log = log;

    public void Generate(IEnumerable<string> cookies, string outputPath)
    {
        using TempFileName tmpFileName = new();
        using (var w = File.CreateText(tmpFileName.Path))
        {
            Emit(w, cookies);
        }

        if (Utils.CopyIfDifferent(tmpFileName.Path, outputPath, useHash: false))
            Log.LogMessage(MessageImportance.Low, $"Generating managed2native table to '{outputPath}'.");
        else
            Log.LogMessage(MessageImportance.Low, $"Managed2native table in {outputPath} is unchanged.");
    }

    private static void Emit(StreamWriter w, IEnumerable<string> cookies)
    {
        w.WriteLine("""
        /*
        * GENERATED FILE, DON'T EDIT
        * Generated by InterpToNativeGenerator
        */

        #include "pinvoke.h"
        #include <stdlib.h>
        """);

        // Use OrderBy because Order() is not available in net472
        var signatures = cookies.OrderBy(c => c).Distinct().ToArray();
        foreach (var signature in signatures)
        {
            try
            {
                w.WriteLine("static void");
                w.WriteLine($"wasm_invoke_{signature.ToLower(CultureInfo.InvariantCulture)} (void *target_func, MonoInterpMethodArguments *margs)");
                w.WriteLine("{");

                w.Write($"\ttypedef {SignatureMapper.CharToNativeType(signature[0])} (*T)(");
                for (int i = 1; i < signature.Length; ++i)
                {
                    char p = signature[i];
                    if (i > 1)
                        w.Write(", ");
                    w.Write($"{SignatureMapper.CharToNativeType(p)} arg_{i - 1}");
                }

                if (signature.Length == 1)
                    w.Write("void");

                w.WriteLine(");\n\tT func = (T)target_func;");

                var ctx = new EmitCtx();

                w.Write("\t");
                if (!SignatureMapper.IsVoidSignature(signature))
                    w.Write($"{SignatureMapper.CharToNativeType(signature[0])} res = ");

                w.Write("func (");
                for (int i = 1; i < signature.Length; ++i)
                {
                    char p = signature[i];
                    if (i > 1)
                        w.Write(", ");
                    w.Write(ctx.Emit(p));
                }
                w.WriteLine(");");

                if (!SignatureMapper.IsVoidSignature(signature))
                {
                    w.WriteLine($"\tvoid *retval = mono_wasm_interp_method_args_get_retval (margs);");
                    w.WriteLine($"\t*({SignatureMapper.CharToNativeType(signature[0])}*)retval = res;");
                }

                w.WriteLine("}\n");
            }
            catch (InvalidSignatureCharException e)
            {
                throw new LogAsErrorException($"Element '{e.Char}' of signature '{signature}' can't be handled by managed2native generator");
            }
        }

        Array.Sort(signatures);

        w.WriteLine("static void* interp_to_native_invokes[] = {");
        foreach (var sig in signatures)
        {
            var lsig = sig.ToLower(CultureInfo.InvariantCulture);
            w.WriteLine($"\twasm_invoke_{lsig},");
        }
        w.WriteLine("};");

        w.WriteLine("static const char* interp_to_native_signatures[] = {");

        foreach (var signature in signatures)
            w.WriteLine($"\t\"{signature}\",");

        w.WriteLine("};");

        w.WriteLine($"static unsigned int interp_to_native_signatures_count = {signatures.Length};");
        w.WriteLine();
        w.WriteLine("""
        static int
        compare_icall_tramp (const void *key, const void *elem)
        {
            return strcmp (key, *(void**)elem);
        }

        static void*
        mono_wasm_interp_to_native_callback (char* cookie)
        {
            void* p = bsearch (cookie, interp_to_native_signatures, interp_to_native_signatures_count, sizeof (void*), compare_icall_tramp);
            if (!p)
                return NULL;
            int idx = (const char**)p - (const char**)interp_to_native_signatures;
            return interp_to_native_invokes [idx];
        };
        """);
    }

    private sealed class EmitCtx
    {
        private int iarg, farg;

        public string Emit(char c)
        {
            int argIndex;
            switch (c)
            {
                case 'I':
                    argIndex = iarg;
                    iarg += 1;
                    break;
                case 'L':
                    argIndex = iarg;
                    iarg += 2;
                    break;
                case 'F':
                case 'D':
                    argIndex = farg;
                    farg += 1;
                    break;
                default:
                    throw new InvalidSignatureCharException(c);
            }

            return $"mono_wasm_interp_method_args_get_{char.ToLower(c, CultureInfo.InvariantCulture)}arg (margs, {argIndex})";
        }
    }
}
